package world.geometry;
import javax.media.j3d.Appearance;
import javax.media.j3d.ColoringAttributes;
import javax.media.j3d.Geometry;
import javax.media.j3d.Material;
import javax.media.j3d.PolygonAttributes;
import javax.media.j3d.QuadArray;
import javax.media.j3d.Shape3D;
import javax.media.j3d.TransformGroup;
import javax.media.j3d.Transform3D;
import javax.media.j3d.TransparencyAttributes;
import javax.vecmath.Point3d;
import javax.vecmath.Vector3d;

public class Goblet extends Shape3D {
	
	/*Enable/Disable debug*/
	private boolean DEBUG = false;
	
    private double umin_ = -1.0;
    private double umax_ = 1.0;
    private double delta_u_ = 0.1;
    private double delta_v_ = 0.1;
	
    public Goblet () {

	setGeometry (myGeometry_ ());
	setAppearance (myAppearance_ ());
	//this.setAllUserData(this.tg);
    }

    private Geometry myGeometry_ () {

	double cubicPoints[] = calc_cubic_points_ (umin_, umax_, delta_u_);
        double patchPoints[] = rotation_solid_ (cubicPoints, delta_v_);

	int cols = cubicPoints.length / 3;
	int rows = (int)(3.14159 * 2.0 / delta_v_) + 1;
	int num_faces = (rows - 1) * (cols - 1);
	int num_verts = num_faces * 4;

	QuadArray result = new QuadArray (num_verts,
                                  QuadArray.COORDINATES | QuadArray.NORMALS);
	double quadFaces[] = calc_faces_ (patchPoints, rows, cols);
	float quadNormals[] = calc_normals_ (quadFaces, rows, cols);
	result.setCoordinates(0, quadFaces);
	result.setNormals(0, quadNormals);
	return result;
    }

    private Appearance myAppearance_ () {

	Appearance result = new Appearance ();
	Material material = new Material ();
	material.setShininess (128.0f);
	result.setMaterial (material);
	ColoringAttributes ca = result.getColoringAttributes ();
	if (ca == null) {
	
	    ca = new ColoringAttributes ();
        }
        ca.setShadeModel (ColoringAttributes.SHADE_GOURAUD);
	result.setColoringAttributes (ca);
        PolygonAttributes pa = result.getPolygonAttributes ();
	if (pa == null) {

	    pa = new PolygonAttributes ();
	}
	pa.setCullFace (PolygonAttributes.CULL_NONE);
	result.setPolygonAttributes (pa);
	TransparencyAttributes ta = new TransparencyAttributes (
		TransparencyAttributes.NICEST, 0.75f);
	result.setTransparencyAttributes (ta);
	return result;
    }

    private double[] calc_cubic_points_ (
	double umin, 
	double umax, 
	double delta) {

	int num_points = (int)((umax - umin) / delta) + 1;
	double[] points = new double[3*num_points];

	double x;
	double y;

	for (int i = 0; i < num_points; i++) {

	    x = umin + (double)i * delta;
	    // my cubic of choice
	    y = (5.0 * x * x * x - 3.0 * x * x - 5.0 * x - 2.0) / 10.0; 
	    //y = 0.5;

	    points[3*i] = x;
	    points[3*i+1] = y;
	    points[3*i+2] = 0.0;
	}
	return points;
    }

    private void printArray_ (double[] array) {

	for (int i = 0; i < array.length; i++) {

	    System.out.print (array[i] + "  ");
	    if (((i +1) % 3) == 0)
		System.out.println ();
	}
    }

    private double[] rotation_solid_ (
		double[] line, 
		double delta_v) {
	
		int row_points = line.length / 3;
		int col_points = (int)(3.14159 * 2.0 / delta_v) + 1;
		double patch[] = new double[row_points * col_points * 3];
	
		Transform3D cur_rotation = new Transform3D ();
		Point3d point = new Point3d ();
		double angle;
		int base_idx;
		
		for (int v = 0; v < col_points-1; v++) {
		    angle = (double)v * delta_v_;
		    cur_rotation.rotX (angle);
		    if(DEBUG)
		    	System.out.println ("v = " + v +" angle = " + angle);
		    for (int u = 0; u < row_points; u++) {
	
			point.set (line[u*3], line[u*3+1], line[u*3+2]);
			cur_rotation.transform (point);
		        base_idx = (row_points*v + u) * 3;
		        patch[base_idx] = point.x;
		        patch[base_idx + 1] = point.y;
		        patch[base_idx + 2] = point.z;
		    }
		}
		// copy first row to last row
		int last_row_idx = row_points * (col_points - 1) * 3;
		for (int i = 0; i < row_points*3; i++) {
	
		    patch [last_row_idx + i] = patch[i];
		}
		    
		return patch;
    }

    private double[] calc_faces_ (
	double[] patch,
	int num_rows,
	int num_cols) {

	int size = (num_rows - 1) * (num_cols - 1) * 12;
	double[] result = new double[size];
        int face_number;
	int face_start_idx;
	int base_idx;

	for (int v = 0; v < num_rows - 1; v++) {  // row major

	    for (int u = 0; u < num_cols - 1; u++) { 

	        face_number = v * (num_cols - 1) + u;
		face_start_idx = face_number * 12;

	        // top left vertex for this face
	        base_idx = (v * num_cols + u) * 3;
	        result[face_start_idx] = patch[base_idx];
	        result[face_start_idx+1] = patch[base_idx+1];
	        result[face_start_idx+2] = patch[base_idx+2];

	        // bottom left vertex for this face
	        base_idx = ((v+1) * num_cols + u) * 3;
	        result[face_start_idx+3] = patch[base_idx];
	        result[face_start_idx+4] = patch[base_idx+1];
	        result[face_start_idx+5] = patch[base_idx+2];

	        // bottom right vertex for this face
	        base_idx = ((v+1) * num_cols + u+1) * 3;
	        result[face_start_idx+6] = patch[base_idx];
	        result[face_start_idx+7] = patch[base_idx+1];
	        result[face_start_idx+8] = patch[base_idx+2];

	        // top right vertex for this face
	        base_idx = (v * num_cols + u+1) * 3;
	        result[face_start_idx+9] = patch[base_idx];
	        result[face_start_idx+10] = patch[base_idx+1];
	        result[face_start_idx+11] = patch[base_idx+2];
  	    }
        }
	return result;
    }

    private float[] calc_normals_ (
	double[] faces,
	int num_rows,
	int num_cols) {

	int size = (num_rows - 1) * (num_cols - 1) * 12;
	float[] result = new float[size];
	float[] face_normals = new float[size/4];

	int face_idx;
	Vector3d v1 = new Vector3d ();
	Vector3d v2 = new Vector3d ();
	Vector3d normal = new Vector3d ();

	// pass 1 - calculate the normal for each face
	for (int v = 0; v < num_rows - 1; v++) {

	    for (int u = 0; u < num_cols - 1; u++) {

		face_idx = (v * (num_cols-1) + u) * 12;

		// v1 goes from top left vertex to top right vertex
		v1.x = faces[face_idx+9] - faces[face_idx];
		v1.y = faces[face_idx+10] - faces[face_idx+1];
		v1.z = faces[face_idx+11] - faces[face_idx+2];

		// v2 goes from top left vertex to bottom left vertex
		v2.x = faces[face_idx+3] - faces[face_idx];
		v2.y = faces[face_idx+4] - faces[face_idx+1];
		v2.z = faces[face_idx+5] - faces[face_idx+2];

	        normal.cross (v2, v1);
	  	normal.normalize ();

		face_idx /= 4;
		face_normals[face_idx] = (float)normal.x;
		face_normals[face_idx+1] = (float)normal.y;
		face_normals[face_idx+2] = (float)normal.z;
            }
        }

	// pass 2 - calculate vertex normals as averages of
	// the normals of adjacent faces for interior (ie
	// non-boundary) vertices.
	int tr_face_idx;
	int tl_face_idx;
	int br_face_idx;
	int bl_face_idx;
        int top;
	int bottom;
        int left;
	int right;
	for (int v = 0; v < num_rows - 1; v++) {

	    for (int u = 0; u < num_cols - 1; u++) {

		// top left 
	        top = (v==0)? v : v-1;
		bottom = v;
		left = (u==0)? u : u-1;
		right = u;
	        tl_face_idx = (top * (num_cols-1) + left) * 3;
	        tr_face_idx = (top * (num_cols-1) + right) * 3;
	        bl_face_idx = (bottom * (num_cols-1) + left) * 3;
	        br_face_idx = (bottom * (num_cols-1) + right) * 3;

		normal.x = face_normals[tl_face_idx] +
			   face_normals[tr_face_idx] +
			   face_normals[bl_face_idx] +
			   face_normals[br_face_idx];

		normal.y = face_normals[tl_face_idx+1] +
			   face_normals[tr_face_idx+1] +
			   face_normals[bl_face_idx+1] +
			   face_normals[br_face_idx+1];

		normal.z = face_normals[tl_face_idx+2] +
			   face_normals[tr_face_idx+2] +
			   face_normals[bl_face_idx+2] +
			   face_normals[br_face_idx+2];

		normal.normalize ();

		face_idx = (v * (num_cols-1) + u) * 12;
		result[face_idx] = (float)normal.x;
		result[face_idx+1] = (float)normal.y;
		result[face_idx+2] = (float)normal.z;

		// bottom left
	        top = v;
		bottom = (v>=num_rows-2)? v : v+1;
		left = (u==0)? u : u-1;
		right = u;
	        tl_face_idx = (top * (num_cols-1) + left) * 3;
	        tr_face_idx = (top * (num_cols-1) + right) * 3;
	        bl_face_idx = (bottom * (num_cols-1) + left) * 3;
	        br_face_idx = (bottom * (num_cols-1) + right) * 3;

		normal.x = face_normals[tl_face_idx] +
			   face_normals[tr_face_idx] +
			   face_normals[bl_face_idx] +
			   face_normals[br_face_idx];

		normal.y = face_normals[tl_face_idx+1] +
			   face_normals[tr_face_idx+1] +
			   face_normals[bl_face_idx+1] +
			   face_normals[br_face_idx+1];

		normal.z = face_normals[tl_face_idx+2] +
			   face_normals[tr_face_idx+2] +
			   face_normals[bl_face_idx+2] +
			   face_normals[br_face_idx+2];

		normal.normalize ();

		result[face_idx+3] = (float)normal.x;
		result[face_idx+4] = (float)normal.y;
		result[face_idx+5] = (float)normal.z;

		// bottom right 
	        top = v;
		bottom = (v>=num_rows-2)? v : v+1;
		left = u;
		right = (u>=num_cols-2)? u : u+1;
	        tl_face_idx = (top * (num_cols-1) + left) * 3;
	        tr_face_idx = (top * (num_cols-1) + right) * 3;
	        bl_face_idx = (bottom * (num_cols-1) + left) * 3;
	        br_face_idx = (bottom * (num_cols-1) + right) * 3;

		normal.x = face_normals[tl_face_idx] +
			   face_normals[tr_face_idx] +
			   face_normals[bl_face_idx] +
			   face_normals[br_face_idx];

		normal.y = face_normals[tl_face_idx+1] +
			   face_normals[tr_face_idx+1] +
			   face_normals[bl_face_idx+1] +
			   face_normals[br_face_idx+1];

		normal.z = face_normals[tl_face_idx+2] +
			   face_normals[tr_face_idx+2] +
			   face_normals[bl_face_idx+2] +
			   face_normals[br_face_idx+2];

		normal.normalize ();

		result[face_idx+6] = (float)normal.x;
		result[face_idx+7] = (float)normal.y;
		result[face_idx+8] = (float)normal.z;

		// top right 
	        top = (v==0)? v : v-1;
		bottom = v;
		left = u;
		right = (u>=num_cols-2)? u : u+1;
	        tl_face_idx = (top * (num_cols-1) + left) * 3;
	        tr_face_idx = (top * (num_cols-1) + right) * 3;
	        bl_face_idx = (bottom * (num_cols-1) + left) * 3;
	        br_face_idx = (bottom * (num_cols-1) + right) * 3;

		normal.x = face_normals[tl_face_idx] +
			   face_normals[tr_face_idx] +
			   face_normals[bl_face_idx] +
			   face_normals[br_face_idx];

		normal.y = face_normals[tl_face_idx+1] +
			   face_normals[tr_face_idx+1] +
			   face_normals[bl_face_idx+1] +
			   face_normals[br_face_idx+1];

		normal.z = face_normals[tl_face_idx+2] +
			   face_normals[tr_face_idx+2] +
			   face_normals[bl_face_idx+2] +
			   face_normals[br_face_idx+2];

		normal.normalize ();

		result[face_idx+9] = (float)normal.x;
		result[face_idx+10] = (float)normal.y;
		result[face_idx+11] = (float)normal.z;
            }
        }
	return result;
    }

    private float[] calc_normals_old_ (
		double[] faces,
		int num_rows,
		int num_cols) {
	
		int size = (num_rows - 1) * (num_cols - 1) * 12;
		float[] result = new float[size];
	
		int face_idx;
		Vector3d v1 = new Vector3d ();
		Vector3d v2 = new Vector3d ();
		Vector3d normal = new Vector3d ();
		
		if(DEBUG)
			System.out.println ("faces.length = " + faces.length +
				    " num_cols = " + num_cols +
				    " num_rows = " + num_rows +
				    "\n(num_cols-1)*(num_rows-1)*12 = " +
				    ((num_cols-1)*(num_rows-1)*12));
		// pass 1 - calculate the normal for each face
		for (int v = 0; v < num_rows - 1; v++) {
	
		    for (int u = 0; u < num_cols - 1; u++) {
	
			face_idx = (v * (num_cols-1) + u) * 12;
	
			// v1 goes from top left vertex to top right vertex
		        //System.out.println ("face_idx = " + face_idx);
			v1.x = faces[face_idx+9] - faces[face_idx];
			v1.y = faces[face_idx+10] - faces[face_idx+1];
			v1.z = faces[face_idx+11] - faces[face_idx+2];
	
			// v2 goes from top left vertex to bottom left vertex
			v2.x = faces[face_idx+3] - faces[face_idx];
			v2.y = faces[face_idx+4] - faces[face_idx+1];
			v2.z = faces[face_idx+5] - faces[face_idx+2];
	
		        normal.cross (v2, v1);
		  	normal.normalize ();
	
			// top left vertex
			result[face_idx] = (float)normal.x;
			result[face_idx+1] = (float)normal.y;
			result[face_idx+2] = (float)normal.z;
	
			// bottom left vertex
			result[face_idx+3] = (float)normal.x;
			result[face_idx+4] = (float)normal.y;
			result[face_idx+5] = (float)normal.z;
	
			// bottom right vertex
			result[face_idx+6] = (float)normal.x;
			result[face_idx+7] = (float)normal.y;
			result[face_idx+8] = (float)normal.z;
	
			// top right vertex
			result[face_idx+9] = (float)normal.x;
			result[face_idx+10] = (float)normal.y;
			result[face_idx+11] = (float)normal.z;
	            }
	        }
		return result;
    }
    
 
}
